import numpy as np

__all__ = ['crossover_1point', 'crossover_2point', 'crossover_2point_bit', 'crossover_pmx', 'crossover_2point_prob']


def crossover_1point(self):
    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
    for i in range(0, size_pop, 2):
        n = np.random.randint(0, self.len_chrom)
        # crossover at the point n
        seg1, seg2 = self.Chrom[i, n:].copy(), self.Chrom[i + 1, n:].copy()
        self.Chrom[i, n:], self.Chrom[i + 1, n:] = seg2, seg1
    return self.Chrom


def crossover_2point(self):
    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
    for i in range(0, size_pop, 2):
        n1, n2 = np.random.randint(0, self.len_chrom, 2)
        if n1 > n2:
            n1, n2 = n2, n1
        # crossover at the points n1 to n2
        seg1, seg2 = self.Chrom[i, n1:n2].copy(), self.Chrom[i + 1, n1:n2].copy()
        self.Chrom[i, n1:n2], self.Chrom[i + 1, n1:n2] = seg2, seg1
    return self.Chrom


def crossover_2point_bit(self):
    '''
    3 times faster than `crossover_2point`, but only use for 0/1 type of Chrom
    :param self:
    :return:
    '''
    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
    half_size_pop = int(size_pop / 2)
    Chrom1, Chrom2 = Chrom[:half_size_pop], Chrom[half_size_pop:]
    mask = np.zeros(shape=(half_size_pop, len_chrom), dtype=self.number_type)
    for i in range(half_size_pop):
        n1, n2 = np.random.randint(0, self.len_chrom, 2)
        if n1 > n2:
            n1, n2 = n2, n1
        mask[i, n1:n2] = 1
    mask2 = (Chrom1 ^ Chrom2) & mask
    Chrom1 ^= mask2
    Chrom2 ^= mask2
    return self.Chrom


def crossover_2point_prob(self, crossover_prob):
    '''
    2 points crossover with probability
    '''
    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
    for i in range(0, size_pop, 2):
        if np.random.rand() < crossover_prob:
            n1, n2 = np.random.randint(0, self.len_chrom, 2)
            if n1 > n2:
                n1, n2 = n2, n1
            seg1, seg2 = self.Chrom[i, n1:n2].copy(), self.Chrom[i + 1, n1:n2].copy()
            self.Chrom[i, n1:n2], self.Chrom[i + 1, n1:n2] = seg2, seg1
    return self.Chrom


# def crossover_rv_3(self):
#     Chrom, size_pop = self.Chrom, self.size_pop
#     i = np.random.randint(1, self.len_chrom)  # crossover at the point i
#     Chrom1 = np.concatenate([Chrom[::2, :i], Chrom[1::2, i:]], axis=1)
#     Chrom2 = np.concatenate([Chrom[1::2, :i], Chrom[0::2, i:]], axis=1)
#     self.Chrom = np.concatenate([Chrom1, Chrom2], axis=0)
#     return self.Chrom


def crossover_pmx(self):
    '''
    Executes a partially matched crossover (PMX) on Chrom.
    For more details see [Goldberg1985]_.

    :param self:
    :return:

    .. [Goldberg1985] Goldberg and Lingel, "Alleles, loci, and the traveling
   salesman problem", 1985.
    '''
    tmpChrom_index = np.where(self.Chrom != -1)
    tmpChrom = self.Chrom[tmpChrom_index].reshape(self.size_pop, -1) - 1

    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom
    for i in range(0, size_pop, 2):

        Chrom1, Chrom2 = tmpChrom[i], tmpChrom[i + 1]
        cxpoint1, cxpoint2 = np.random.randint(0, self.len_chrom - 1, 2)
        if cxpoint1 >= cxpoint2:
            cxpoint1, cxpoint2 = cxpoint2, cxpoint1 + 1
        # crossover at the point cxpoint1 to cxpoint2
        pos1_recorder = {value: idx for idx, value in enumerate(Chrom1)}
        pos2_recorder = {value: idx for idx, value in enumerate(Chrom2)}
        for j in range(cxpoint1, cxpoint2):
            value1, value2 = Chrom1[j], Chrom2[j]
            pos1, pos2 = pos1_recorder[value2], pos2_recorder[value1]
            Chrom1[j], Chrom1[pos1] = Chrom1[pos1], Chrom1[j]
            Chrom2[j], Chrom2[pos2] = Chrom2[pos2], Chrom2[j]
            pos1_recorder[value1], pos1_recorder[value2] = pos1, j
            pos2_recorder[value1], pos2_recorder[value2] = j, pos2

            tmpChrom[i], tmpChrom[i + 1] = Chrom1, Chrom2

        # 重排
    self.Chrom[:] = -1
    self.Chrom[tmpChrom_index] = tmpChrom.reshape(-1) + 1

    return self.Chrom


def muti_crossover_pmx(self):
    '''
    Executes a partially matched crossover (PMX) on Chrom.
    For more details see [Goldberg1985]_.

    :param self:
    :return:

    .. [Goldberg1985] Goldberg and Lingel, "Alleles, loci, and the traveling
   salesman problem", 1985.
    '''
    tmpChrom_index = np.where(self.Chrom != 0)
    tmpChrom = self.Chrom[tmpChrom_index].reshape(self.size_pop, -1) - 1

    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom

    cxpoint1, cxpoint2 = np.random.randint(0, self.len_chrom - 1, (2, size_pop // 2))
    cxswap = cxpoint1 >= cxpoint2
    cxpoint1[cxswap], cxpoint2[cxswap] = cxpoint2[cxswap], cxpoint1[cxswap] + 1
    pos_recorder = np.ones_like(tmpChrom)
    pos_recorder[
        np.arange(pos_recorder.size).reshape([-1, self.len_chrom]) // self.len_chrom, tmpChrom] = np.arange(
        self.len_chrom)
    for i in range(0, self.len_chrom):
        sel = np.where((cxpoint1 <= i) & (i < cxpoint2))[0]
        sel1, sel2 = sel * 2, sel * 2 + 1

        value1, value2 = tmpChrom[sel1, i], tmpChrom[sel2, i]
        pos1, pos2 = pos_recorder[sel1, value2], pos_recorder[sel2, value1]
        tmpChrom[sel1, i], tmpChrom[sel1, pos1] = tmpChrom[sel1, pos1], tmpChrom[sel1, i]
        tmpChrom[sel2, i], tmpChrom[sel2, pos2] = tmpChrom[sel2, pos2], tmpChrom[sel2, i]
        pos_recorder[sel1, value1], pos_recorder[sel1, value2] = pos_recorder[sel1, value2], i
        pos_recorder[sel2, value1], pos_recorder[sel2, value2] = i, pos_recorder[sel2, value1]

        # 重排
    self.Chrom[:] = 0
    self.Chrom[tmpChrom_index] = tmpChrom.reshape(-1) + 1
    return self.Chrom


def muti_crossover_pmx2(self):
    '''
    Executes a partially matched crossover (PMX) on Chrom.
    For more details see [Goldberg1985]_.

    :param self:
    :return:

    .. [Goldberg1985] Goldberg and Lingel, "Alleles, loci, and the traveling
   salesman problem", 1985.
    '''
    tmpChrom_index = self.Chrom != 0
    tmpChrom = self.Chrom[tmpChrom_index].reshape(self.size_pop, -1) - 1

    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom

    cxpoint1, cxpoint2 = np.random.randint(0, self.len_chrom - 1, (2, size_pop // 2))
    cxswap = cxpoint1 >= cxpoint2
    cxpoint1[cxswap], cxpoint2[cxswap] = cxpoint2[cxswap], cxpoint1[cxswap] + 1
    pos_recorder = np.ones_like(tmpChrom)
    pos_recorder[
        np.arange(pos_recorder.size).reshape([-1, self.len_chrom]) // self.len_chrom, tmpChrom] = np.arange(
        self.len_chrom)
    # carlist cross
    for i in range(0, self.len_chrom):
        sel = np.where((cxpoint1 <= i) & (i < cxpoint2))[0]
        sel1, sel2 = sel * 2, sel * 2 + 1

        value1, value2 = tmpChrom[sel1, i], tmpChrom[sel2, i]
        pos1, pos2 = pos_recorder[sel1, value2], pos_recorder[sel2, value1]
        tmpChrom[sel1, i], tmpChrom[sel1, pos1] = tmpChrom[sel1, pos1], tmpChrom[sel1, i]
        tmpChrom[sel2, i], tmpChrom[sel2, pos2] = tmpChrom[sel2, pos2], tmpChrom[sel2, i]
        pos_recorder[sel1, value1], pos_recorder[sel1, value2] = pos_recorder[sel1, value2], i
        pos_recorder[sel2, value1], pos_recorder[sel2, value2] = i, pos_recorder[sel2, value1]

    # deltaT cross
    self.tmpChrom_index = tmpChrom_index
    self.tmpChrom = tmpChrom
    return self.Chrom


def muti_crossover_pmx3(self):
    '''
    Executes a partially matched crossover (PMX) on Chrom.
    For more details see [Goldberg1985]_.

    :param self:
    :return:

    .. [Goldberg1985] Goldberg and Lingel, "Alleles, loci, and the traveling
   salesman problem", 1985.
    '''
    tmpChrom_index = self.Chrom != 0
    tmpChrom = self.Chrom[tmpChrom_index].reshape(-1, self.len_chrom) - 1

    Chrom, size_pop, len_chrom = self.Chrom, self.size_pop, self.len_chrom

    cxpoint1, cxpoint2 = np.random.randint(0, self.len_chrom - 1, (2, size_pop // 2))
    cxswap = cxpoint1 >= cxpoint2
    cxpoint1[cxswap], cxpoint2[cxswap] = cxpoint2[cxswap], cxpoint1[cxswap] + 1
    pos_recorder = np.ones_like(tmpChrom)
    pos_recorder[
        np.arange(pos_recorder.size).reshape([-1, self.len_chrom]) // self.len_chrom, tmpChrom] = np.arange(
        self.len_chrom)
    # carlist cross
    for i in range(0, self.len_chrom):
        sel = np.where((cxpoint1 <= i) & (i < cxpoint2))[0]
        sel1, sel2 = sel * 2, sel * 2 + 1

        value1, value2 = tmpChrom[sel1, i], tmpChrom[sel2, i]
        pos1, pos2 = pos_recorder[sel1, value2], pos_recorder[sel2, value1]
        tmpChrom[sel1, i], tmpChrom[sel1, pos1] = tmpChrom[sel1, pos1], tmpChrom[sel1, i]
        tmpChrom[sel2, i], tmpChrom[sel2, pos2] = tmpChrom[sel2, pos2], tmpChrom[sel2, i]
        pos_recorder[sel1, value1], pos_recorder[sel1, value2] = pos_recorder[sel1, value2], i
        pos_recorder[sel2, value1], pos_recorder[sel2, value2] = i, pos_recorder[sel2, value1]

    # deltaT cross
    self.tmpChrom_index = tmpChrom_index
    self.tmpChrom = tmpChrom
    return self.Chrom
